﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows;

namespace SqlMiM.Common.UserControls.PerfGraph
{
    public class HistoryData : INotifyPropertyChanged
    {
        // Fields
        private Brush _fill;
        private Pen _stroke;
        private HistoryDataType type;

        private bool _hasData;
        private int _numNonZeroToAdd;
        private int _numNonZeroValues;
        private List<TimeValueNode> _values;
        private List<TimeValueNode> _valuesToAdd;


        #region Propriétés 

        public TimeSpan Duration
        {
            get;
            set;
        }

        /// <summary>
        /// Couleur de remplissage (si Type = HistoryDataType.Shape)
        /// </summary>
        public Brush Fill
        {
            get
            {
                return this._fill;
            }
            set
            {
                if (this._fill != value)
                {
                    this._fill = value;
                    this.NotifyPropertyChanged("Fill");
                }
            }
        }

        /// <summary>
        /// Couleur du trait (si Type = HistoryDataType.Path)
        /// </summary>
        public Pen Stroke
        {
            get
            {
                return this._stroke;
            }
            set
            {
                if (this._stroke != value)
                {
                    this._stroke = value;
                    this.NotifyPropertyChanged("Stroke");
                }
            }
        }

        /// <summary>
        /// Détermine si le rendu est un "trait" ou une "figure pleine"
        /// </summary>
        public HistoryDataType Type
        {
            get
            {
                return this.type;
            }
            set
            {
                if (this.type != value)
                {
                    this.type = value;
                    this.NotifyPropertyChanged("Type");
                }
            }
        }

        public bool HasData
        {
            get
            {
                return this._hasData;
            }
            private set
            {
                if (this._hasData != value)
                {
                    this._hasData = value;
                    this.NotifyPropertyChanged("HasData");
                }
            }
        }

        public string Name
        {
            get;
            set;

        }

        private int NonZeroValueCount
        {
            get
            {
                return this._numNonZeroValues;
            }
            set
            {
                if (this._numNonZeroValues != value)
                {
                    this._numNonZeroValues = value;
                    this.HasData = this._numNonZeroValues > 0;
                }
            }
        }

        #endregion


        #region PropertyChanged

        // Events
        public event PropertyChangedEventHandler PropertyChanged;
        
        private void NotifyPropertyChanged(string info)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion


        #region Constructeur
        
        public HistoryData()
        {
            this._values = new List<TimeValueNode>();
            this._valuesToAdd = new List<TimeValueNode>();
        }

        public HistoryData(string name)
            : this()
        {
            this.Name = name;
        }

        public HistoryData(string name, TimeSpan duration)
            : this(name)
        {
            this.Duration = duration;
        }

        #endregion


        /// <summary>
        /// Ajoute une donnée à l'HistoryData dans un buffer 
        /// </summary>
        public void AddData(DateTime timeStamp, double value)
        {
            bool hasData = this.HasData;

            TimeValueNode item = new TimeValueNode(timeStamp, value);
            
            this._valuesToAdd.Add(item);

            if (value != 0.0)
                this._numNonZeroToAdd++;
        }

        /// <summary>
        /// Raz des données
        /// </summary>
        public void ClearData()
        {
            if (this.HasData)
            {
                this._values.Clear();
                this.NonZeroValueCount = 0;
            }
            this._valuesToAdd.Clear();
            this._numNonZeroToAdd = 0;
        }

        /// <summary>
        /// Recalcul des points 
        /// Important : Va calculer l'ensemble des points par rapport à l'ensemble des datas en buffer
        /// Les points sont plus nombreux que le buffer entre deux intervalles. va calculer une moyenne
        /// </summary>
        public Point[] ComputePoints(DateTime currentTime)
        {
            // Création d'une liste de points
            List<Point> list = new List<Point>();

            if (this.HasData && (list.Count == 0))
            {
                list.Clear();

                int valuesCount = this._values.Count;
                
                int indexValue = 0;

                // Parcours de toutes les valeurs
                for (int i = 0; i < valuesCount; i++)
                {
                    // Récupération de la données (pair date / valeur)
                    TimeValueNode node = this._values[i];
                    TimeSpan span = (TimeSpan)(currentTime - node.TimeStamp);
                    Point currentItem = new Point(span.TotalSeconds, node.Value);

                    // Si j'ai au moins 3 points
                    if (indexValue >= 2)
                    {
                        // Récupération du point placé 2 index avant
                        Point point2 = list[indexValue - 2];
                        
                        // Si ce point a le même Y
                        if (point2.Y == currentItem.Y)
                        {
                            // Récupération du point du milieu (index -1)
                            Point point3 = list[indexValue - 1];

                            // Si ce point a lui aussi le même Y.
                            // On le remplace par le courant
                            if (point3.Y == currentItem.Y)
                            {
                                list[indexValue - 1] = currentItem;
                                continue;
                            }
                        }
                    }
                    list.Add(currentItem);
                    indexValue++;
                }
                list.Reverse();
            }
            return list.ToArray();
        }

        /// <summary>
        /// Met à jour les données
        /// Récupère les données du buffer et le vide
        /// Enlève les données qui ont dépassé la durée (Duration) de l'HistoryData
        /// </summary>
        public void UpdateData(DateTime currentTime)
        {
            // Résoud le buffer
            if (this._valuesToAdd.Count > 0)
            {
                bool hasData = this.HasData;
                this._values.AddRange(this._valuesToAdd);
                this._valuesToAdd.Clear();
                this._values.Sort();
                this.NonZeroValueCount += this._numNonZeroToAdd;
                this._numNonZeroToAdd = 0;
            }

            int count = this._values.Count;

            int numbersOfNonZeroValue = 0;
            
            int indexValue = 0;
            
            while (indexValue < count)
            {
                TimeValueNode node = this._values[indexValue];
                
                TimeSpan span = (TimeSpan)(currentTime - node.TimeStamp);
                
                if (span <= this.Duration)
                    break;

                if (node.Value != 0.0)
                    numbersOfNonZeroValue++;

                indexValue++;
            }
            if (indexValue > 0)
            {
                this._values.RemoveRange(0, indexValue);

                this.NonZeroValueCount -= numbersOfNonZeroValue;
            }
        }

        /// <summary>
        /// Représente pour un date, une valeur
        /// </summary>
        private class TimeValueNode : IComparable<HistoryData.TimeValueNode>
        {
            public DateTime TimeStamp
            {
                get;
                set;
            }

            public double Value
            {
                get;
                set;
            }

            // Methods
            public TimeValueNode(DateTime timeStamp, double value)
            {
                this.TimeStamp = timeStamp;
                this.Value = value;
            }

            public int CompareTo(HistoryData.TimeValueNode other)
            {
                return this.TimeStamp.CompareTo(other.TimeStamp);
            }

        }
    }

    public enum HistoryDataType
    {
        Shape,
        Path,
    }
}
